package bancosys.tec.persist.dao;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.cache.ExpiringMap;
import jmine.tec.persist.PersistMessages;
import jmine.tec.persist.chinese.ChineseWallFilterRegistry;
import jmine.tec.persist.naturalkey.InvalidNaturalKeyException;
import jmine.tec.persist.naturalkey.NaturalKeyHandler;
import jmine.tec.persist.naturalkey.NaturalKeyHandlerFactory;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;

import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.Filter;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.authorization.Authorizable;
import bancosys.tec.persist.authorization.AuthorizationContext;
import bancosys.tec.persist.controller.PersistenceEnabledController;
import bancosys.tec.persist.dataset.HibernateRandomAccessResultSet;
import bancosys.tec.persist.exception.NaturalKeyNotDefinedException;
import bancosys.tec.persist.exception.PersistenceException;
import bancosys.tec.persist.hibernate.PartitionSessionFactory;
import bancosys.tec.persist.hibernate.RestrictionsUtils;
import bancosys.tec.persist.hibernate.impl.annotations.AnnotationPartitionSessionFactory;
import bancosys.tec.persist.hibernate.page.PagingService;
import bancosys.tec.persist.persister.PersistenceAction;
import bancosys.tec.persist.spring.transaction.PartitionTransactionDefinition;
import bancosys.tec.query.QueryParameter;
import bancosys.tec.query.exception.QueryParameterException;
import bancosys.tec.security.chinesewall.ChineseWallContext;
import bancosys.tec.security.chinesewall.ChineseWallContextHolder;

/**
 * Classe base para os DAOS. Possui métodos comuns para a utilização dos DAOS.
 * 
 * @param <BO> classe gerenciada pelo DAO.
 */
@SuppressWarnings("deprecation")
@Unmodifiable
public abstract class BaseDAO<BO extends Persistable> {

    private static final int DEFAULT_TIMEOUT = 600;

    private ChineseWallFilterRegistry chineseWallFilterRegistry;

    private PagingService pagingService;

    protected PersistenceEnabledController controller = null;

    private PlatformTransactionManager transactionManager;

    private NaturalKeyHandlerFactory naturalKeyHandlerFactory;
    
    private final ExpiringMap<Serializable, Object> lastKnownVersions = new ExpiringMap<Serializable, Object>(true, DEFAULT_TIMEOUT,
            TimeUnit.SECONDS);

    private Boolean hasVersion;

    private String versionProperty;

    /**
     * C'tor
     */
    public BaseDAO() {
    }

    /**
     * Define o controller que deve ser usado pelo dao.
     * 
     * @param controller o controller que deve ser usado pelo dao.
     */
    public void setController(PersistenceEnabledController controller) {
        this.controller = controller;
    }

    /**
     * @param chineseWallFilterRegistry the chineseWallFilterRegistry to set
     */
    public void setChineseWallFilterRegistry(ChineseWallFilterRegistry chineseWallFilterRegistry) {
        this.chineseWallFilterRegistry = chineseWallFilterRegistry;
    }

    /**
     * Devolve a session do hibernate.
     * 
     * @return a session do hibernate.
     */
    public Session getSession() {
        if (this.controller == null) {
            throw new NullPointerException("Controller nao injetado!");
        }
        return this.controller.getSessionFactory().getCurrentSession();
    }

    /**
     * Cria uma instância do bean e injeta os persisters.
     * 
     * @return uma nova instância do bean.
     */
    @SuppressWarnings("unchecked")
    public BO createBean() {
        BO bean;
        try {
            Class clazz = this.getPersistableClass();
            Constructor constructor = clazz.getDeclaredConstructor((Class[]) null);
            if (!Modifier.isPublic(constructor.getModifiers()) || !Modifier.isPublic(constructor.getDeclaringClass().getModifiers())) {
                constructor.setAccessible(true);
            }
            bean = (BO) constructor.newInstance(new Object[0]);
            bean.setDirty(true);
        } catch (InstantiationException e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_BEAN.create(this.getPersistableClass().getName()), e);
        } catch (IllegalAccessException e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_BEAN.create(this.getPersistableClass().getName()), e);
        } catch (SecurityException e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_BEAN.create(this.getPersistableClass().getName()), e);
        } catch (NoSuchMethodException e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_BEAN.create(this.getPersistableClass().getName()), e);
        } catch (IllegalArgumentException e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_BEAN.create(this.getPersistableClass().getName()), e);
        } catch (InvocationTargetException e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_INSTANTIATE_BEAN.create(this.getPersistableClass().getName()), e);
        }
        this.injectController(bean);
        return bean;
    }

    /**
     * Executa uma query hql, utiliza paginacao. Atenção: usar preferencialmente o {@link #executeQuery(String, Map)} que retorna o
     * resultado já tipado.
     * 
     * @deprecated
     * @param query query hql
     * @param properties properties para binding na query.
     * @return <code>Collection</code> com o resultado, se nenhum bean for encontrado a <code>Collection</code> devolvida estará vazia.
     */
    @Deprecated
    protected List<?> executeUnsafeTypeQuery(String query, Map<String, Object> properties) {
        this.filterChineseWall();
        try {
            Query queryObject = this.paginateHQLQuery(query, properties);
            bindParameters(queryObject, properties);
            return queryObject.list();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        }
    }

    /**
     * Executa uma Query no banco de dados fazendo bind dos parâmetros fornecidos.
     * 
     * @param query Uma Query
     * @param properties O Mapa de parâmetros
     * @deprecated Só é necessário para dar suporte a HQL
     */
    @Deprecated
    private void executeQueryUpdate(Query query, Map<String, Object> properties) {
        try {
            bindParameters(query, properties);
            query.executeUpdate();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_UPDATE.create(), e);
        }
    }

    /**
     * Executa uma Query no banco de dados fazendo bind dos parâmetros fornecidos.
     * 
     * @param query Uma Query
     * @param properties O Mapa de parâmetros
     * @return O BO
     * @throws BeanNotFoundException Se a query não tiver registros
     * @deprecated Só é necessário para dar suporte a HQL
     */
    @Deprecated
    @SuppressWarnings("unchecked")
    private BO executeSingleQuery(Query query, Map<String, Object> properties) throws BeanNotFoundException {
        try {
            bindParameters(query, properties);
            List list = query.list();
            if (list.size() == 0) {
                throw new BeanNotFoundException(this.getPersistableClass());
            }
            if (list.size() > 1) {
                throw new TooManyBeansFoundException(this.getPersistableClass());
            }
            return (BO) list.iterator().next();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        }
    }

    /**
     * Executa uma Query no banco de dados fazendo bind dos parâmetros fornecidos.
     * 
     * @param query Uma Query
     * @param properties O Mapa de parâmetros
     * @return A lista de BO
     * @deprecated Só é necessário para dar suporte a HQL
     */
    @Deprecated
    @SuppressWarnings("unchecked")
    private List<BO> executeQuery(Query query, Map<String, Object> properties) {
        try {
            bindParameters(query, properties);
            return query.list();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        }
    }

    /**
     * Executa uma Query no banco de dados fazendo bind dos parâmetros fornecidos.
     * 
     * @param query Uma Query
     * @param parameters Os parâmetros
     */
    private void executeQueryUpdate(Query query, Object... parameters) {
        try {
            this.bindParameters(query, parameters);
            query.executeUpdate();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_UPDATE.create(), e);
        }
    }

    /**
     * Executa uma Query no banco de dados fazendo bind dos parâmetros fornecidos.
     * 
     * @param query Uma Query
     * @param parameters Os parâmetros
     * @return O BO
     * @throws BeanNotFoundException Se a query não tiver registros
     */
    @SuppressWarnings("unchecked")
    private BO executeSingleQuery(Query query, Object... parameters) throws BeanNotFoundException {
        try {
            this.bindParameters(query, parameters);
            List list = query.list();
            if (list.size() == 0) {
                throw new BeanNotFoundException(this.getPersistableClass());
            }
            if (list.size() > 1) {
                throw new TooManyBeansFoundException(this.getPersistableClass());
            }
            return (BO) list.iterator().next();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        }
    }

    /**
     * Executa uma Query no banco de dados fazendo bind dos parâmetros fornecidos.
     * 
     * @param query Uma Query
     * @param parameters Os parâmetros
     * @return A lista de BO
     */
    @SuppressWarnings("unchecked")
    private List<BO> executeQuery(Query query, Object... parameters) {
        try {
            this.bindParameters(query, parameters);
            return query.list();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        }
    }

    /* LISTAGENS */

    /**
     * Executa uma query hql.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: SIM
     * 
     * @param hqlQuery query hql
     * @return <code>Collection</code> com o resultado, se nenhum bean for encontrado a <code>Collection</code> devolvida estará vazia.
     * <p>
     * @deprecated Para queries dinâmicas ou que necessitam de paginação utilize Criteria. Nos outros casos utilize NamedQuery.
     */
    @Deprecated
    protected List<BO> executeQuery(String hqlQuery) {
        return this.executeQuery(hqlQuery, null);
    }

    /**
     * Executa uma query hql, utiliza paginacao. O retorno será uma lista do mesmo tipo definido no DAO.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: SIM
     * 
     * @param hqlQuery query hql
     * @param properties properties para binding na query.
     * @return <code>List</code> com o resultado já tipado, se nenhum bean for encontrado a <code>List</code> devolvida estará vazia.
     * <p>
     * @deprecated Para queries dinâmicas ou que necessitam de paginação utilize Criteria. Nos outros casos utilize NamedQuery.
     */
    @Deprecated
    protected List<BO> executeQuery(String hqlQuery, Map<String, Object> properties) {
        this.filterChineseWall();
        Query query = this.paginateHQLQuery(hqlQuery, properties);
        return this.executeQuery(query, properties);
    }

    /**
     * Executa um Criteria.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: SIM
     * 
     * @param criteria criteria.
     * @return <code>Collection</code> com o resultado, se nenhum bean for encontrado a <code>Collection</code> devolvida estará vazia.
     */
    // @SuppressWarnings("unchecked")
    protected List<BO> executeQuery(Criteria criteria) {
        this.filterChineseWall();
        try {
            return this.paginateExecuteQuery(criteria);
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        }
    }

    /**
     * Busca que garante não retornar mais de um elemento da mesma entidade. Devido a um antigo bug do hibernate, não é possivel utilizar
     * DISTINCT junto com paginação: pois os elementos duplicados são removidos após a paginação, devolvendo menos elementos por página que
     * deveria.
     * 
     * @see http://blog.xebia.com/2008/12/11/sorting-and-pagination-with-hibernate-criteria-how-it-can-go-wrong-with-joins/
     * @see http://www.hibernate.org/117.html#A12
     * @param dc a Criteria
     * @param order Ordenacao da query.
     * @return <code>Collection</code> com o resultado, se nenhum bean for encontrado a <code>Collection</code> devolvida estará vazia.
     */
    protected List<BO> executeDistinctQuery(DetachedCriteria dc, Order order) {
        if (dc != null) {
            dc.setProjection(Projections.id());
            Criteria result = this.createCriteria();
            result.add(Subqueries.propertyIn("id", dc));
            if (order != null) {
                result.addOrder(order);
            }
            return this.executeQuery(result);
        }
        return new ArrayList<BO>();
    }

    /**
     * Executa uma NamedQuery com ChineseWall.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: NÃO
     * 
     * @param queryName String
     * @param parameters Object[]
     * @return List
     */
    protected final List<BO> executeNamedQuery(String queryName, Object... parameters) {
        this.filterChineseWall();
        Query query = this.getSession().getNamedQuery(queryName);
        return this.executeQuery(query, parameters);
    }

    /* SINGLES */

    /**
     * Executa uma query hql que busca apenas um bean.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: SIM
     * 
     * @param hqlQuery query hql
     * @return bean procurado
     * @throws BeanNotFoundException quando nenhum resultado é encontrado.
     * <p>
     * @deprecated Para queries dinâmicas ou que necessitam de paginação utilize Criteria. Nos outros casos utilize NamedQuery.
     */
    @Deprecated
    protected BO executeSingleQuery(String hqlQuery) throws BeanNotFoundException {
        return this.executeSingleQuery(hqlQuery, null);
    }

    /**
     * Executa uma query hql que busca apenas um bean.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: SIM
     * 
     * @param hqlQuery query hql
     * @param properties properties para binding na query.
     * @return bean procurado
     * @throws BeanNotFoundException quando nenhum resultado é encontrado.
     * <p>
     * @deprecated Para queries dinâmicas ou que necessitam de paginação utilize Criteria. Nos outros casos utilize NamedQuery.
     */
    @Deprecated
    protected BO executeSingleQuery(String hqlQuery, Map<String, Object> properties) throws BeanNotFoundException {
        this.filterChineseWall();
        Query query = this.paginateHQLQuery(hqlQuery, properties);
        return this.executeSingleQuery(query, properties);
    }

    /**
     * Executa um Criteria que busca apenas um bean.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: SIM
     * 
     * @param query query hql
     * @return bean procurado.
     * @throws BeanNotFoundException quando nenhum resultado é encontrado.
     */
    @SuppressWarnings("unchecked")
    protected BO executeSingleQuery(Criteria query) throws BeanNotFoundException {
        List c = this.executeQuery(query);
        if (c.size() == 0) {
            throw new BeanNotFoundException(this.getPersistableClass());
        }
        if (c.size() > 1) {
            throw new TooManyBeansFoundException(this.getPersistableClass());
        }
        return (BO) c.iterator().next();
    }

    /**
     * Executa uma named query que busca apenas um bean.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: NÃO
     * 
     * @param queryName nome da namedQuery
     * @param parameters Object[]
     * @return bean procurado
     * @throws BeanNotFoundException quando nenhum resultado é encontrado.
     */
    protected final BO executeSingleNamedQuery(String queryName, Object... parameters) throws BeanNotFoundException {
        this.filterChineseWall();
        Query query = this.getSession().getNamedQuery(queryName);
        return this.executeSingleQuery(query, parameters);
    }

    /* UPDATES */

    /**
     * Executa uma query hql que não devolve resultado (insert, delete).
     * <p>
     * ChineseWall: SIM
     * 
     * @param hqlQuery query hql
     */
    @Deprecated
    protected void executeUpdate(String hqlQuery) {
        this.executeUpdate(hqlQuery, null);
    }

    /**
     * Executa uma query hql que não devolve resultado (insert, delete).
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: SIM
     * 
     * @param hqlQuery query hql
     * @param properties properties para binding na query.
     */
    @Deprecated
    protected void executeUpdate(String hqlQuery, Map<String, Object> properties) {
        this.filterChineseWall();
        Query query = this.getSession().createQuery(hqlQuery);
        this.executeQueryUpdate(query, properties);
    }

    /**
     * Executa uma NamedQuery de insert ou delete.
     * <p>
     * ChineseWall: SIM
     * 
     * @param queryName O nome da query
     * @param parameters Object[]
     */
    protected final void executeNamedQueryUpdate(String queryName, Object... parameters) {
        this.filterChineseWall();
        Query query = this.getSession().getNamedQuery(queryName);
        this.executeQueryUpdate(query, parameters);
    }

    /**
     * Retorna a quantidade de elementos do resultado de uma pesquisa.
     * 
     * @param criteria a pesquisa
     * @return quantidade de elementos do seu resultado
     */
    protected int executeCount(Criteria criteria) {
        this.filterChineseWall();
        criteria.setProjection(Projections.rowCount());
        return ((Integer) criteria.list().get(0)).intValue();
    }

    /**
     * Liga os valores dos parâmetros nas queries.
     * 
     * @param query query.
     * @param properties parâmetros.
     * @deprecated Só serve para dar suporte a HQL
     */
    @Deprecated
    public static void bindParameters(Query query, Map<String, Object> properties) {
        if (properties != null) {
            Set<Entry<String, Object>> entrySet = properties.entrySet();
            for (Entry<String, Object> entry : entrySet) {
                if (entry.getValue() != null) {
                    if (entry.getValue() instanceof Collection) {
                        query.setParameterList(entry.getKey(), (Collection) entry.getValue());
                    } else if (entry.getValue().getClass().isArray()) {
                        query.setParameterList(entry.getKey(), (Object[]) entry.getValue());
                    } else {
                        query.setParameter(entry.getKey(), entry.getValue());
                    }
                } else {
                    query.setParameter(entry.getKey(), null);
                }
            }
        }
    }

    /**
     * Adiciona os parâmetros na query. A ordem dos valores deve bater com a ordem definida na Query.
     * 
     * @param query Query
     * @param values Object[]
     */
    private void bindParameters(Query query, Object... values) {
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                query.setParameter(i, values[i]);
            }
        }
    }

    /**
     * Busca no banco todos os elementos dessa classe.
     * 
     * @return todos os elementos persistidos dessa classe.
     */
    public List<BO> findAll() {
        return this.executeQuery(this.getSession().createCriteria(this.getPersistableClass()));
    }

    /**
     * Busca no banco todos os elementos dessa classe.
     * 
     * @param orders Orders para ordenar o resultado
     * @return todos os elementos persistidos dessa classe.
     */
    public List<BO> findAll(Order... orders) {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        if (orders != null) {
            for (Order order : orders) {
                criteria.addOrder(order);
            }
        }
        return this.executeQuery(criteria);
    }

    /**
     * Busca no banco todos os elementos dessa classe ordenados pela pk
     * 
     * @return todos os elementos persistidos dessa classe ordenados pela pk.
     */
    public List<BO> findAllOrderedByPk() {
        return this.executeQuery(this.getSession().createCriteria(this.getPersistableClass()).addOrder(Order.asc("id")));
    }

    /**
     * Finds all elements ordered by their natural keys
     * 
     * @return List of business objects
     */
    public List<BO> findAllOrderedByNk() {
        Criteria criteria = this.createCriteria();
        this.getNaturalKeyHandler().addOrderAsc(criteria);
        return this.executeQuery(criteria);
    }

    /**
     * Busca elemento por primary key.
     * 
     * @param pk primary key.
     * @return o elemento com a primary key passada.
     * @throws BeanNotFoundException erro na camada de persistencia.
     */
    @SuppressWarnings("unchecked")
    public BO findByPk(Serializable pk) throws BeanNotFoundException {
        Class<? extends BO> clazz = this.getPersistableClass();
        if (pk == null) {
            throw new BeanNotFoundException(this.getPersistableClass());
        }
        BO bo = (BO) this.getSession().get(clazz, pk);
        if (bo == null) {
            throw new BeanNotFoundException(this.getPersistableClass());
        }
        if (this.hasVersion()) {
            Object version = this.getClassMetaData().getPropertyValue(bo, this.versionProperty, EntityMode.POJO);
            if (version != null) {
                this.lastKnownVersions.put(pk, version);
            }
        }
        return bo;
    }

    /**
     * Devolve uma lista das entidades cujas propriedades estão de acordo com o exemplo passado por parâmetro. Para montar o objeto
     * <code>Example</code> utilizado na busca é utilizado o método <code>mountExample</code>. Esse método deve ser sobrescrito por
     * subclasses que precisem tratar associações, já que a API de criteria do Hibernate não faz isso automaticamente.
     * 
     * @param exampleInstance o exemplo.
     * @param excludeProperties as propriedades que devem ser ignoradas no exemplo.
     * @param literal indica se a query deve ser literal ou deve utilizar <code>ignoreCase</code> e <code>like</code>.
     * @return a lista das entidades.
     * @see #mountExample(Persistable, String[], boolean)
     * @deprecated Não utilizar, causa bugs incompreensíveis. Crie um query apropriada.
     */
    @Deprecated
    public List<BO> findByExample(BO exampleInstance, boolean literal, String... excludeProperties) {
        Criteria crit = this.getSession().createCriteria(this.getPersistableClass());
        crit.add(this.mountExample(exampleInstance, literal, excludeProperties));
        return this.executeQuery(crit);
    }

    /**
     * Retorna a entidade a partir do valor de sua NaturalKey. Este método depende da presença da anotação <code>@NaturalKey</code> na
     * entidade pesquisada, ou em alguma super-classe da entidade. Lança NaturalKeyNotDefinedException caso não encontre a anotação
     * <code>@NaturalKey</code> na entidade
     * 
     * @param naturalKey valor da naturalKey
     * @return a entidade a partir do valor de sua NaturalKey.
     * @throws BeanNotFoundException caso não encontre nenhuma entidade com a naturalKey passada
     */
    public BO findByNaturalKey(Serializable naturalKey) throws BeanNotFoundException {
        Criteria criteria = this.createCriteria();
        try {
            this.getNaturalKeyHandler().addRestrictionEq(criteria, naturalKey);
        } catch (InvalidNaturalKeyException e) {
            throw new BeanNotFoundException(this.getPersistableClass());
        }
        return this.executeSingleQuery(criteria);
    }

    /**
     * @param naturalKeyPartial String o natural key
     * @return {@link List} de BOClass
     */
    public List<BO> findByLikeNaturalKey(String naturalKeyPartial) {
        if (naturalKeyPartial == null || "".equals(naturalKeyPartial)) {
            return this.findAllOrderedByNk();
        }
        try {
            Criteria criteria = this.createCriteria();
            this.getNaturalKeyHandler().addRestrictionLike(criteria, naturalKeyPartial, MatchMode.ANYWHERE);
            this.getNaturalKeyHandler().addOrderAsc(criteria);
            return this.executeQuery(criteria);
        } catch (InvalidNaturalKeyException e) {
            return Collections.emptyList();
        }
    }

    /**
     * Devolve uma lista de um determinado campo das entidades cujos valores contem propertyValue. O campo devolvido eh determinado pelo
     * parametro projectionProperty.
     * <p>
     * O resultado passa pelo chinese wall mas nao passa pela paginacao
     * 
     * @param <T> o tipo da projecao
     * @param projectionType o tipo da projecao
     * @param propertyValue {@link String}
     * @param propertyName {@link String}
     * @param projectionProperty {@link String}
     * @return uma lista de um determinado campo especificado por propertyName.
     */
    @SuppressWarnings("unchecked")
    public <T> List<? extends T> findByPropertyLikeProjectProperty(String propertyValue, String propertyName, String projectionProperty,
            Class<T> projectionType) {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        criteria.add(Restrictions.ilike(propertyName, propertyValue));
        criteria.setProjection(Projections.property(propertyName));
        criteria.addOrder(Order.asc(propertyName));
        this.filterChineseWall();
        try {
            return criteria.list();
        } catch (HibernateException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        }
    }

    /**
     * Devolve uma lista das entidades cujas propriedades estão de acordo com o exemplo passado por parâmetro. Para montar o objeto
     * <code>Example</code> utilizado na busca é utilizado o método <code>mountExample</code>. Esse método deve ser sobrescrito por
     * subclasses que precisem tratar associações, já que a API de criteria do Hibernate não faz isso automaticamente.
     * 
     * @param exampleInstance o exemplo.
     * @param excludeProperties as propriedades que devem ser ignoradas no exemplo.
     * @param literal indica se a query deve ser literal ou deve utilizar <code>ignoreCase</code> e <code>like</code>.
     * @return a lista das entidades.
     * @see #mountExample(Persistable, String[], boolean)
     * @deprecated Não utilizar, causa bugs incompreensíveis. Crie um query apropriada.
     */
    @Deprecated
    public HibernateRandomAccessResultSet<BO> findResultSetByExample(BO exampleInstance, boolean literal, String... excludeProperties) {
        Criteria crit = this.getSession().createCriteria(this.getPersistableClass());
        crit.add(this.mountExample(exampleInstance, literal, excludeProperties));
        return new HibernateRandomAccessResultSet<BO>(this.getSession(), crit);
    }

    /**
     * Método utilizado internamente pelo método <code>findByExample</code> para montar o objeto <code>Example</code> utilizado na busca.
     * 
     * @param exampleInstance o exemplo.
     * @param excludeProperties as propriedades que devem ser ignoradas no exemplo.
     * @param literal indica se a query deve ser literal ou deve utilizar <code>ignoreCase</code> e <code>like</code>.
     * @return o objeto <code>Example</code> utilizado pelo método <code>findByExample</code>.
     * @see #findByExample(Persistable, String[], boolean)
     * @deprecated Não utilizar, causa bugs incompreensíveis. Crie um query apropriada.
     */
    @Deprecated
    protected Example mountExample(Persistable exampleInstance, boolean literal, String... excludeProperties) {
        Example example = Example.create(exampleInstance);

        if (!literal) {
            example = example.ignoreCase().enableLike(MatchMode.ANYWHERE).excludeZeroes();
        }

        example.excludeProperty(AnnotationPartitionSessionFactory.PARAMETER_NAME);

        if (excludeProperties != null) {
            for (String exclude : excludeProperties) {
                example.excludeProperty(exclude);
            }
        }

        return example;
    }

    /**
     * Adciona uma busca por uma determinada entidade associada a entidade atual utilizando id se este estiver preenchido, ou example caso
     * contrário.
     * 
     * @param critParam o objeto <code>Criteria</code> que contém a query parcialmente montada
     * @param literal indica se a query deve ser literal ou deve utilizar <code>ignoreCase</code> e <code>like</code>.
     * @param bo o objeto que será utilizado como exemplo
     * @param propertyName o nome da propriedade pela qual a associação é feita
     * @param excludeProperties as propriedades que devem ser ignoradas no exemplo.
     * @return o objeto <code>Criteria</code> alterado.
     * @deprecated Não utilizar, causa bugs incompreensíveis. Crie um query apropriada.
     */
    @Deprecated
    protected Criteria addExampleCriteria(Criteria critParam, boolean literal, Persistable bo, String propertyName,
            String... excludeProperties) {
        Criteria crit = critParam;
        if (bo != null) {
            if (bo.getPk() != null) {
                crit = crit.createAlias(propertyName, propertyName).add(Restrictions.eq(propertyName + ".id", bo.getPk()));
            } else {
                crit = crit.createCriteria(propertyName).add(this.mountExample(bo, literal, excludeProperties));
            }
        }
        return crit;
    }

    /**
     * Adciona uma busca por uma determinada entidade associada a entidade atual utilizando id se este estiver preenchido, ou example caso
     * contrário.
     * 
     * @param dc o objeto <code>Criteria</code> que contém a query parcialmente montada
     * @param literal indica se a query deve ser literal ou deve utilizar <code>ignoreCase</code> e <code>like</code>.
     * @param bo o objeto que será utilizado como exemplo
     * @param propertyName o nome da propriedade pela qual a associação é feita
     * @param excludeProperties as propriedades que devem ser ignoradas no exemplo.
     * @return o objeto <code>Criteria</code> alterado.
     * @deprecated Não utilizar, causa bugs incompreensíveis. Crie um query apropriada.
     */
    @Deprecated
    protected DetachedCriteria addExampleDetachedCriteria(DetachedCriteria dc, boolean literal, Persistable bo, String propertyName,
            String... excludeProperties) {
        DetachedCriteria crit = dc;
        if (bo != null) {
            if (bo.getPk() != null) {
                crit = crit.createAlias(propertyName, propertyName).add(Restrictions.eq(propertyName + ".id", bo.getPk()));
            } else {
                crit = crit.createCriteria(propertyName).add(this.mountExample(bo, literal, excludeProperties));
            }
        }
        return crit;
    }

    /**
     * Carrega um bean da partição quente pelo id.
     * 
     * @param id id do bean
     * @return bean
     */
    @SuppressWarnings("unchecked")
    public BO forceHotLoadByPk(Serializable id) {
        final SessionFactory sessionFactory = this.controller.getSessionFactory();
        if (sessionFactory instanceof PartitionSessionFactory) {
            PartitionSessionFactory partitionSessionFactory = (PartitionSessionFactory) sessionFactory;
            Session session = partitionSessionFactory.getCurrentSession();
            AuthorizationContext context = partitionSessionFactory.changeSessionAuthorizationContext(session, AuthorizationContext.hot);
            BO result = (BO) session.load(this.getPersistableClass(), id);
            partitionSessionFactory.changeSessionAuthorizationContext(session, context);
            return result;
        }
        return (BO) this.getSession().load(this.getPersistableClass(), id);
    }

    /**
     * Dado um bean da base quente remove toda informação associada á ele que estiver na base fria.
     * 
     * @param bean bean.
     * @return número de entradas removidas da base fria.
     */
    public int removeNotAuthorizedData(Authorizable bean) {
        if (!(this.controller.getSessionFactory() instanceof PartitionSessionFactory)) {
            return 0;
        }

        PartitionSessionFactory sessionFactory = (PartitionSessionFactory) this.controller.getSessionFactory();
        if (!sessionFactory.getBoAuthorizationManager().isAuthorizable(bean)) {
            return 0;
        }

        if (bean.getAuth()) {
            throw new PersistenceException(PersistMessages.BEAN_NOT_AUTHORIZED.create(bean));
        }
        PartitionSessionFactory partitionSessionFactory = (PartitionSessionFactory) this.controller.getSessionFactory();
        Session session = partitionSessionFactory.getCurrentSession();
        AuthorizationContext context = partitionSessionFactory.changeSessionAuthorizationContext(session, AuthorizationContext.cold);
        try {
            Query query = session.createQuery("DELETE FROM " + this.getPersistableClass().getName() + " WHERE authId = :authId");
            query.setParameter("authId", bean.getPk());
            return query.executeUpdate();
        } finally {
            partitionSessionFactory.changeSessionAuthorizationContext(session, context);
        }
    }

    /**
     * Carrega todos os beans desse tipo da partição quente.
     * 
     * @return lista com todos os beans desse tipo da partição quente.
     */
    public List<BO> forceHotLoadAll() {
        return this.executeHotQuery(DetachedCriteria.forClass(this.getPersistableClass()));
    }

    /**
     * Executa uma query na partição quente.
     * 
     * @param criteria query
     * @return lista com todos os beans desse tipo da partição quente.
     */
    @SuppressWarnings("unchecked")
    protected List<BO> executeHotQuery(DetachedCriteria criteria) {
        final SessionFactory sf = this.controller.getSessionFactory();
        Session session = sf.getCurrentSession();
        if (sf instanceof PartitionSessionFactory) {
            PartitionSessionFactory partitionSessionFactory = (PartitionSessionFactory) sf;
            AuthorizationContext context = partitionSessionFactory.changeSessionAuthorizationContext(session, AuthorizationContext.hot);
            try {
                return criteria.getExecutableCriteria(session).list();
            } finally {
                partitionSessionFactory.changeSessionAuthorizationContext(session, context);
            }
        } else {
            return criteria.getExecutableCriteria(session).list();
        }
    }

    /**
     * Executa uma query hql na partição quente, utiliza paginacao.
     * 
     * @param hqlQuery query hql
     * @param properties properties para binding na query.
     * @return <code>Collection</code> com o resultado, se nenhum bean for encontrado a <code>Collection</code> devolvida estará vazia.
     * <p>
     * @deprecated Para queries dinâmicas ou que necessitam de paginação utilize Criteria. Nos outros casos utilize NamedQuery.
     */
    @Deprecated
    protected List<BO> executeHotQuery(String hqlQuery, Map<String, Object> properties) {
        final SessionFactory sf = this.controller.getSessionFactory();
        Session session = sf.getCurrentSession();
        if (sf instanceof PartitionSessionFactory) {
            PartitionSessionFactory partitionSessionFactory = (PartitionSessionFactory) sf;
            AuthorizationContext context = partitionSessionFactory.changeSessionAuthorizationContext(session, AuthorizationContext.hot);
            try {
                return this.executeQuery(hqlQuery, properties);
            } finally {
                partitionSessionFactory.changeSessionAuthorizationContext(session, context);
            }
        } else {
            return this.executeQuery(hqlQuery, properties);
        }
    }

    /**
     * Executa uma NamedQuery na partição quente.
     * <p>
     * ChineseWall: SIM
     * <p>
     * Paginação: NÃO
     * 
     * @param queryName nome da query
     * @param properties properties para binding na query.
     * @return <code>Collection</code> com o resultado, se nenhum bean for encontrado a <code>Collection</code> devolvida estará vazia.
     */
    protected final List<BO> executeHotNamedQuery(String queryName, Object... properties) {
        final SessionFactory sf = this.controller.getSessionFactory();
        Session session = sf.getCurrentSession();
        if (sf instanceof PartitionSessionFactory) {
            PartitionSessionFactory partitionSessionFactory = (PartitionSessionFactory) sf;
            AuthorizationContext context = partitionSessionFactory.changeSessionAuthorizationContext(session, AuthorizationContext.hot);
            try {
                return this.executeNamedQuery(queryName, properties);
            } finally {
                partitionSessionFactory.changeSessionAuthorizationContext(session, context);
            }
        } else {
            return this.executeNamedQuery(queryName, properties);
        }
    }

    /**
     * Executa query na base quente usando query parameter.
     * 
     * @param queryParameter query
     * @return bos encontrados pela busca.
     * @deprecated usar {@link BaseDAO#executeHotQuery(DetachedCriteria)}
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    protected List<BO> executeHotQuery(QueryParameter queryParameter) {
        PartitionSessionFactory partitionSessionFactory = (PartitionSessionFactory) this.controller.getSessionFactory();
        Session session = partitionSessionFactory.getCurrentSession();
        AuthorizationContext context = partitionSessionFactory.changeSessionAuthorizationContext(session, AuthorizationContext.hot);
        try {
            return (List<BO>) queryParameter.executeQuery(session);
        } catch (QueryParameterException e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        } finally {
            partitionSessionFactory.changeSessionAuthorizationContext(session, context);
        }
    }

    /**
     * Pagina a query e executa
     * 
     * @param crit {@link Criteria}
     * @return {@link List}
     */
    @SuppressWarnings("unchecked")
    private List<BO> paginateExecuteQuery(Criteria crit) {
        this.getPagingService().paginate(crit);
        return crit.list();
    }

    /**
     * Pagina uma query utilizando o serviço de paginação, se a session factory nao suportar o serviço de paginação a query não será
     * paginada.
     * 
     * @param hqlQuery query.
     * @param properties properties bara binding na query
     * @return query paginada.
     * @deprecated Só serve para oferecer suporte a HQL
     */
    @Deprecated
    private Query paginateHQLQuery(String hqlQuery, Map<String, Object> properties) {
        Session session = this.getSession();
        return this.getPagingService().paginate(session, hqlQuery, properties);
    }

    /**
     * Devolve a classe gerenciada pelo DAO.
     * 
     * @return a classe gerenciada pelo DAO.
     */
    @SuppressWarnings("unchecked")
    protected Class<? extends BO> getPersistableClass() {
        try {
            TypeVariable typeVariable = BaseDAO.class.getTypeParameters()[0];
            return (Class<? extends BO>) GenericTypeResolverImpl.getInstance()
                    .resolveTypeForFixedGenericType(typeVariable, this.getClass());
        } catch (IllegalArgumentException e) {
            // Erro de codificação no " + this.getClass() + ", a classe não está usando generics como deveria.
            throw new PersistenceException(PersistMessages.INVALID_BEAN_IMPLEMENTATION.create(this.getClass()));
        }
    }

    /**
     * Injeta o persiter no bean.
     * 
     * @param bean bean.
     */
    protected void injectController(Persistable bean) {
        bean.setController(this.controller);
    }

    /**
     * Faz merge (hibernate) do objeto.
     * 
     * @param bean objeto.
     * @return objeto "merged".
     */
    @SuppressWarnings("unchecked")
    public BO merge(BO bean) {
        return (BO) this.getSession().merge(bean);
    }

    /**
     * Reconecta o objeto a sessão da persistência.
     * 
     * @param bean objeto.
     */
    public void reconnect(BO bean) {
        this.getSession().lock(bean, LockMode.NONE);
    }

    /**
     * Resolve o proxy de um objeto se necessario.
     * 
     * @param lazyObject objeto persistido, pode estar com lazy ou não
     * @return instancia real do objeto.
     */
    @SuppressWarnings("unchecked")
    public BO narrow(Persistable lazyObject) {
        BO resolvedObject = null;

        if (lazyObject == null) {
            return null;
        }

        if (lazyObject instanceof HibernateProxy) {
            resolvedObject = (BO) ((HibernateProxy) lazyObject).getHibernateLazyInitializer().getImplementation();
        } else {
            resolvedObject = (BO) lazyObject;
        }

        return resolvedObject;
    }

    /**
     * Executa uma query abrindo uma nova sessão. Os objetos retornados na lista não resolvem lazy, já que a sessão será fechada no final do
     * método.
     * 
     * @param criteria Criteria
     * @return List do BoClass.
     */
    @SuppressWarnings("unchecked")
    public List<BO> executeHotQueryWithNewSession(DetachedCriteria criteria) {
        List<BO> result;
        PartitionTransactionDefinition txDefinition = new PartitionTransactionDefinition();
        txDefinition.setPartition(AuthorizationContext.hot);
        txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus tx = this.transactionManager.getTransaction(txDefinition);

        Session session = this.controller.getSessionFactory().openSession();
        try {
            Criteria executableCriteria = criteria.getExecutableCriteria(session);
            result = executableCriteria.list();
            this.transactionManager.rollback(tx);
        } catch (Throwable e) {
            throw new PersistenceException(PersistMessages.ERROR_EXECUTING_QUERY.create(), e);
        } finally {
            session.close();
        }
        return result;
    }

    /**
     * Liga o filtro de chinese wall na sessão caso esteja habilitado no contexto.
     */
    protected void filterChineseWall() {
        ChineseWallContext context = ChineseWallContextHolder.get();

        for (String filterName : this.chineseWallFilterRegistry.getFilterNames()) {
            if (context != null && context.isEnabled() && context.isFilterEnabled(filterName)) {
                Filter filter = this.getSession().enableFilter(filterName);
                for (Object parameterName : filter.getFilterDefinition().getParameterNames()) {
                    Collection<? extends Serializable> value = context.getParameter(filterName, parameterName.toString());
                    if (value == null || value.isEmpty()) {
                        value = Collections.singleton(-1L);
                    }
                    filter.setParameterList(parameterName.toString(), value);
                }
            } else {
                this.getSession().disableFilter(filterName);
            }

        }

    }

    /**
     * Método para contar quantidade de registros na lista, sem trazer o objeto.
     * 
     * @return int quantidade.
     */
    public int countAll() {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        return this.executeCount(criteria);
    }

    /**
     * Método para contar quantidade de registros na lista, sem trazer o objeto.
     * 
     * @return int quantidade.
     */
    public int countBeansToAuthorize() {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        criteria.add(Restrictions.ne("persistenceAction", PersistenceAction.insertWAction));
        criteria.add(Restrictions.ne("persistenceAction", PersistenceAction.updateWAction));
        return this.executeCount(criteria);
    }

    /**
     * Lista os registros em autorização.
     * 
     * @return lista dos registros em autorização.
     */
    public List<BO> findBeansToAuthorize() {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        criteria.add(Restrictions.ne("persistenceAction", PersistenceAction.insertWAction));
        criteria.add(Restrictions.ne("persistenceAction", PersistenceAction.updateWAction));
        return this.executeQuery(criteria);
    }

    /**
     * Método para contar quantidade de registros na lista, sem trazer o objeto.
     * 
     * @return int quantidade.
     */
    public int countBeansWithAction() {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        RestrictionsUtils.addRestrictionIn(criteria, "persistenceAction", new PersistenceAction[]{ PersistenceAction.insertWAction,
                PersistenceAction.updateWAction });
        return this.executeCount(criteria);
    }

    /**
     * Lista os registros em autorização.
     * 
     * @return lista dos registros em autorização.
     */
    public List<BO> findBeansWithAction() {
        Criteria criteria = this.getSession().createCriteria(this.getPersistableClass());
        RestrictionsUtils.addRestrictionIn(criteria, "persistenceAction", new PersistenceAction[]{ PersistenceAction.insertWAction,
                PersistenceAction.updateWAction });
        return this.executeQuery(criteria);
    }

    /**
     * Define o Transaction Manager.
     * 
     * @param transactionManager Transaction Manager.
     */
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /**
     * @param pagingService the pagingService to set
     */
    public void setPagingService(PagingService pagingService) {
        this.pagingService = pagingService;
    }

    /**
     * Devolve o serviço de paginação se a session suportar, <code>null</code> caso contrário.
     * 
     * @return o serviço de paginação se a session suportar, <code>null</code> caso contrário.
     */
    protected PagingService getPagingService() {
        return this.pagingService;
    }

    /**
     * @return the naturalKeyProperties
     */
    protected NaturalKeyHandler getNaturalKeyHandler() {
        NaturalKeyHandler handler = this.naturalKeyHandlerFactory.findNaturalKeyProperties(this.getPersistableClass());
        if (handler == null) {
            throw new NaturalKeyNotDefinedException(this.getPersistableClass());
        }
        return handler;
    }

    /**
     * @param naturalKeyHandlerFactory the naturalKeyHandlerFactory to set
     */
    public void setNaturalKeyHandlerFactory(NaturalKeyHandlerFactory naturalKeyHandlerFactory) {
        this.naturalKeyHandlerFactory = naturalKeyHandlerFactory;
    }

    /**
     * Cria um criteria para busca na classe específica deste DAO.
     * 
     * @return Criteria.
     */
    protected Criteria createCriteria() {
        return this.getSession().createCriteria(this.getPersistableClass());
    }

    /**
     * Cria um criteria detached para busca na classe específica deste DAO.
     * 
     * @return DetachedCriteria.
     */
    protected DetachedCriteria createDetachedCriteria() {
        return DetachedCriteria.forClass(this.getPersistableClass());
    }

    /**
     * {@inheritDoc}
     */
    public Object getVersion(Serializable primaryKey) throws BeanNotFoundException {
        if (!this.hasVersion()) {
            return null;
        }
        String versionColumn = this.versionProperty;
        Criteria crit = this.createCriteria();
        crit.setProjection(Projections.property(versionColumn));
        crit.add(Restrictions.idEq(primaryKey));
        List<?> results = crit.list();
        if (results.isEmpty()) {
            throw new BeanNotFoundException(this.getPersistableClass());
        } else if (results.size() > 1) {
            throw new TooManyBeansFoundException(this.getPersistableClass());
        }
        return results.get(0);
    }

    /**
     * @param left Object
     * @param right Object
     * @return boolean
     */
    @SuppressWarnings("unchecked")
    public static boolean versionGreaterThan(Object left, Object right) {
        if (!(left instanceof Comparable)) {
            throw new IllegalStateException("not a comparable: " + left);
        } else if (!(right instanceof Comparable)) {
            throw new IllegalStateException("not a comparable: " + right);
        } else {
            return ((Comparable) left).compareTo(right) > 0;
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasVersion() {
        if (this.hasVersion == null) {
            ClassMetadata meta = this.getClassMetaData();
            int versionPropertyIndex = meta == null ? -1 : meta.getVersionProperty();
            if (versionPropertyIndex >= 0) {
                this.hasVersion = Boolean.TRUE;
                this.versionProperty = meta.getPropertyNames()[versionPropertyIndex];
            } else {
                this.hasVersion = Boolean.FALSE;
            }
        }
        return this.hasVersion;
    }

    /**
     * Salva a entidade.
     * 
     * @param bean objeto a ser salvo.
     */
    public void save(BO bean) {
        bean.setController(this.controller);
        bean.getPersister().save();
    }

    /**
     * {@inheritDoc}
     */
    public BO fectchIfNewer(Serializable primaryKey, Object previousVersion) throws BeanNotFoundException {
        if (previousVersion == null || !this.hasVersion()) {
            return this.findByPk(primaryKey);
        }
        Object lastKnown = this.lastKnownVersions.get(primaryKey);
        if (lastKnown != null && versionGreaterThan(lastKnown, previousVersion)) {
            return this.findByPk(primaryKey);
        }
        Criteria criteria = this.createCriteria();
        criteria.add(Restrictions.idEq(primaryKey));
        criteria.add(Restrictions.gt(this.versionProperty, previousVersion));
        List list = criteria.list();
        if (list.isEmpty()) {
            return null;
        } else if (list.size() > 1) {
            throw new TooManyBeansFoundException(this.getPersistableClass());
        } else {
            @SuppressWarnings("unchecked")
            BO theBO = (BO) list.get(0);
            Object newVersion = this.getClassMetaData().getPropertyValue(theBO, this.versionProperty, EntityMode.POJO);
            this.lastKnownVersions.put(primaryKey, newVersion);
            return theBO;
        }
    }

    /**
     * @return the {@link ClassMetadata}
     */
    private ClassMetadata getClassMetaData() {
        return this.controller.getSessionFactory().getClassMetadata(this.getPersistableClass());
    }

    /**
     * Procura todos as entidades por id
     * 
     * @param pks array de {@link Serializable}
     * @return {@link List}
     * @throws BeanNotFoundException se algum dos beans nao for encontrado
     */
    public List<BO> findAllByPks(Serializable[] pks) throws BeanNotFoundException {
        List<Serializable> pkList = Arrays.asList(pks);
        final int limit = 500;
        List<BO> result = new ArrayList<BO>(pks.length);
        for (int i = 0; i < pks.length; i += limit) {
            result.addAll(this.doFindByPks(pkList, i, Math.min(i + limit, pks.length)));
        }
        return result;
    }

    /**
     * Procura por pks, guardando na lista passada
     * 
     * @param pks {@link List} de {@link Serializable}
     * @param fromIndex int
     * @param toIndex int
     * @return {@link List}
     */
    @SuppressWarnings("unchecked")
    protected List<BO> doFindByPks(List<Serializable> pks, int fromIndex, int toIndex) {
        Criteria criteria = this.createCriteria();
        RestrictionsUtils.addRestrictionIn(criteria, "id", pks.subList(fromIndex, toIndex));
        return criteria.list();
    }

}
